package com.artollkitplus.benchmark;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.util.Log;
import android.content.res.Resources;
import android.opengl.GLSurfaceView.Renderer;

/**
 * Opens the camera and displays the output on a square (as a texture)
 * @author Tobias Domhan
 */
public class OpenGLCamRenderer implements Renderer{
	private int textureName;
	private float[] square;
	float[] textureCoords = new float[] {// Camera preview
			 0.0f, 0.625f,
			 0.9375f, 0.625f,
			 0.0f, 0.0f,
			 0.9375f, 0.0f};
	//Light definitions
	private float[] ambientlight = {.3f, .3f, .3f, 1f};
	private float[] diffuselight = {.7f, .7f, .7f, 1f};
	private float[] specularlight = {0.6f, 0.6f, 0.6f, 1f};	
	private FloatBuffer textureBuffer;
	private FloatBuffer squareBuffer;
	private boolean frameEnqueued = false;
	private ByteBuffer frameData = null;
	private ReentrantLock frameLock = new ReentrantLock();
	public boolean isTextureInitialized = false;
	private int textureSize = 256;
	private int previewFrameWidth = 256;
	private int previewFrameHeight = 256;
	private MarkerInfo markerInfo;
	private float aspectRatio=1;
	private int mode = GL10.GL_RGB;//mode, being either GL10.GL_RGB or GL10.GL_LUMINANCE 
	
	public OpenGLCamRenderer(Resources res, MarkerInfo markerInfo)  {
		this.markerInfo = markerInfo;
	}
	
	public void onDrawFrame(GL10 gl) {
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
//		if(DEBUG)
//			gl = (GL10) GLDebugHelper.wrap(gl, GLDebugHelper.CONFIG_CHECK_GL_ERROR, log);
		//setupDraw2D(gl);
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrthof(-100f*aspectRatio, 100f*aspectRatio, -100f, 100f, 1.0f, -1.0f); 
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();
gl.glDisable(GL10.GL_DEPTH_TEST);
		gl.glEnable(GL10.GL_TEXTURE_2D);
		
		gl.glDisable(GL10.GL_LIGHTING);
		gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName);
		if (frameEnqueued) {//load new preview frame as a texture, if needed
			frameLock.lock();
			if(!isTextureInitialized) {
				initializeTexture(gl);
			} else {
				//just update the image
				//can we just update a portion(non power of two)?...seems to work
				gl.glTexSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, 0, previewFrameWidth, previewFrameHeight, mode, GL10.GL_UNSIGNED_BYTE, frameData);
			}
			frameLock.unlock();
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
			frameEnqueued = false;
		}
		gl.glColor4f(1, 1, 1, 1f);	
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);//draw camera preview frame:
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);		
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, squareBuffer);
		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);//draw camera square	
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		///////
		markerInfo.draw(gl);
	}
	
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		gl.glViewport(0, 0, width, height);
		aspectRatio = (float)width/(float)height;
		/////setupDraw2D
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrthof(-100.0f*aspectRatio, 100.0f*aspectRatio, -100.0f, 100.0f, 1.0f, -1.0f);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
		gl.glLoadIdentity();	
		/////
		square = new float[] { 	-100f*aspectRatio, -100.0f, -1f,
				 100f*aspectRatio, -100.0f, -1f,
				-100f*aspectRatio, 100.0f, -1f,
				 100f*aspectRatio, 100.0f, -1f };
		squareBuffer = makeFloatBuffer(square);		
		markerInfo.setScreenSize(width, height);
	}
	
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		gl.glClearColor(0,0,0,0);
		gl.glClearDepthf(1.0f);
		gl.glEnable(GL10.GL_TEXTURE_2D);//enable textures:
		gl.glEnable(GL10.GL_CULL_FACE);
		int[] textureNames = new int[1];
		gl.glGenTextures(1, textureNames, 0);//generate texture names:
		textureName = textureNames[0];
		textureBuffer = makeFloatBuffer(textureCoords);
		//lighting
		gl.glEnable(GL10.GL_LIGHTING);
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, makeFloatBuffer(ambientlight));
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, makeFloatBuffer(diffuselight));
		gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, makeFloatBuffer(specularlight));
		gl.glEnable(GL10.GL_LIGHT0);
		gl.glDisable(GL10.GL_COLOR_MATERIAL);
	}
	
	protected static FloatBuffer makeFloatBuffer(float[] arr) {
		ByteBuffer bb = ByteBuffer.allocateDirect(arr.length*4);
		bb.order(ByteOrder.nativeOrder());
		FloatBuffer fb = bb.asFloatBuffer();
		fb.put(arr);
		fb.position(0);
		return fb;
	}

	public void setNextFrame(ByteBuffer buf) {
		this.frameData = buf;
		this.frameEnqueued = true;
	}

	public ReentrantLock getFrameLock() {
		return frameLock;
	}

	public void setPreviewFrameSize(int textureSize, int realWidth, int realHeight) {	
		if(textureSize!=0 && ((textureSize & -textureSize) == textureSize)){
			Log.e("OpenGLCamRenderer setPreviewFrameSize","setPreviewFrameSize textureSize "+textureSize+" realWidth "+realWidth+" realHeight "+realHeight);
			this.textureSize = textureSize;//FIXME
			this.previewFrameHeight = realHeight;
			this.previewFrameWidth = realWidth;
			this.textureCoords = new float[] {//calculate texture coords
					 0.0f, ((float)realHeight)/textureSize,// Camera preview
					 ((float)realWidth)/textureSize, ((float)realHeight)/textureSize,
					 0.0f, 0.0f,
					 ((float)realWidth)/textureSize, 0.0f			 
				};		
			textureBuffer = makeFloatBuffer(textureCoords);
		}else{
			Log.e("OpenGLCamRenderer setPreviewFrameSize","setPreviewFrameSize textureSize is not PowerOfTwo!!!!");
		}
	}
	
	private void initializeTexture (GL10 gl) {
		byte[] frame;
		switch(mode) {
			default:
				mode = GL10.GL_RGB;
			case GL10.GL_RGB:
				frame = new byte[textureSize*textureSize*3];
				Log.e("OpenGLCamRenderer initializeTexture()","initializeTexture()GL10.GL_RGB");
				break;
			case GL10.GL_LUMINANCE:
				Log.e("OpenGLCamRenderer initializeTexture()","initializeTexture()GL10.GL_LUMINANCE");
				frame = new byte[textureSize*textureSize];
				break;
		}
		gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, mode, textureSize,
				textureSize, 0, mode, GL10.GL_UNSIGNED_BYTE ,
				ByteBuffer.wrap(frame));
		isTextureInitialized = true;		
	}
	
	public void setMode(int pMode) {
		switch(pMode) {		
		case GL10.GL_RGB:
		case GL10.GL_LUMINANCE:
			this.mode = pMode;
			break;
		default:
			this.mode = GL10.GL_RGB;
			break;
		}
		if(pMode != this.mode)
			isTextureInitialized = false;
	}
}
